﻿/* 
 * Produzido por:
 *      Daniel Vargas
 *      Danilo Valvezon
 *      Fernanda Osaki
 *      Tiago Ricardo Almeida da Silva
 *      
 * FATEC São Caetano do Sul
 * Jogos NA6
 * */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace bomberboyonline
{
    // Classe que representa a bomba
    class Bomba : Microsoft.Xna.Framework.DrawableGameComponent
    {
        // Declaração das variaveis da classe
        private const int CIMA = 0, DIREITA = 1, BAIXO = 2, ESQUERDA = 3; // constantes que representam a direcao da bomba quando for chutada
        private Texture2D textura; // Variavel que armazena imagem da bomba
        private int direcao, indexdir, contMudImg, contadorVida; // Variaveis de direcao, e de controle de imagem, mudanca de imagem e controlador de vida da bomba
        private int player, posicao,X, Y, W, H; // Variaveis que armazenam qual personagem é o dono da bomba, qual a posicao(quadrante) dela na tela, e quais as posicoes reais dela 
        private int passo; // Variavel que armazena qual o passo que ela ira andar na tela recebendo esse valor baseado na velocidade do player
        private Game game; // Variavel do jogo
        private bool acesa, movimentando; // Flags de controle se está acesa(para controlar tempo de vida) e se está em movimento(para trocar ela de posicao)
        private List<int> posicoesOcupadas; // Referencia da lista de posicoes ocupadas para controlar colisoes
        private List<int> posicoesItens; // Referencia da lista de itens na tela para passar para fogo
        private List<int> posicoesFogo; // Lista de posicoes do Fogo da Bomba, utilazado para criar o fogo gerado pela bomba
        private SoundEffect somExplosao; // Variavel que armazena o som da explosao da Bomba

        // Construtor da classe
        public Bomba(Game game, bool acesa, int player, int posicao, ref List<int> posicoesOcupadas, ref List<int> posicoesItens, ref List<int> posicoesFogo)
            : base(game)
        {
            // Atribuição dos valores passados pelo personagem e a lista de posicoes de itens e posicoes ocupadas por objetos colidiveis
            this.player = player;
            this.game = game;
            this.posicao = posicao;
            this.posicoesFogo = posicoesFogo;
            W = 45; H = 58;
            X = Coordenadas.retangulos[posicao].X;
            Y = Coordenadas.retangulos[posicao].Y;
            this.acesa = acesa;            
            this.posicoesOcupadas = posicoesOcupadas;
            this.posicoesItens = posicoesItens;
            if (acesa) // Caso a bomba esteja acesa(personagem nao contem item detonador) o contador de vida dela eh acionado e a imagem é mudada de tempos em tempos
            {
                indexdir = 4;
                contadorVida = 150;
            }
            else // Senao a bomba tem a imagem do pavil apagado
            {
                indexdir = 5;
            }
            textura = game.Content.Load<Texture2D>("imagens\\bomb");
            somExplosao = game.Content.Load<SoundEffect>("som\\explosao");
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            mudaImagemBomba();
            verificaVidaBomba();
            movimenta();
            base.Update(gameTime);
        }

        // Metodo que controla vida da bomba
        private void verificaVidaBomba()
        {
            if (acesa) // Verifica primeiramente se ela esta acesa
            {
                if (contadorVida <= 0) // se estiver acesa e a vida tiver chego a 0 ela explode
                {                    
                    explode(ref posicoesOcupadas);                    
                }
                else // Se a vida ainda nao chegou a 0 a vida dela é decrementada 
                {
                    contadorVida--;
                }
            }        
        }

        // Metodo que faz a lógica da explosão da bomba
        public void explode(ref List<int> posicoesOcupadas)
        {
            movimentando = false; // Paralisa a bomba
            int poderFogo = incrementaBombaPersonagem(); // Invoca o metodo que devolve a capacidade de lancar outra bomba ao personagem e automaticamente pega a distancia do fogo baseado no poder de fogo do personagem 
            posicoesOcupadas.Remove(posicao); // Remove a bomba da lista de posicoes Ocupadas de objetos colidiveis
            game.Components.Remove(this); // Remove a bomba do jogo
            somExplosao.Play(); // Reproduz o som da explosao
            geraFogo(poderFogo); // Invoca o metodo que cria o fogo na tela
        }

        // Metodo que devolve a capacidade de lancar outra bomba ao personagem e automaticamente pega a distancia do fogo baseado no poder de fogo do personagem 
        private int incrementaBombaPersonagem()
        {
            IEnumerable<Personagem> ps = game.Components.OfType<Personagem>(); // Pega todos os personagens do jogo
            foreach (Personagem p in ps) // faz um for pra cada personagem
            {
                if (p.getPlayer() == this.player) // compara se player é o dono da bomba
                {
                    p.setQtdRestanteBomba(); // se sim incrementa qtdRestanteBomba do player
                    return p.getPoderFogo(); // e retorna o poder do fogo do personagem
                }
            }
            return 1;
        }

        // Metodo que gera o fogo
        private void geraFogo(int poderFogo)
        {
            int poder = 1; // Variavel de controle para um laco de capacidade de poder do fogo da bomba
            int pos = 0; // Variavel de controle da posicao que o fogo será colocado na tela
            bool cima=true, baixo=true, dir=true, esq=true; // Variaveis de controle para criar os fogos nas suas respectivas direcoes e posicoes e caso o fogo anterior da direcao nao colida com algum objeto
            
            // Se não existe fogo na posicao que a bomba explodiu gera o fogo
            if (!posicoesFogo.Contains(posicao))
            {
                Fogo fogoCentro = new Fogo(game, posicao, ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Cria fogo
                posicoesFogo.Add(posicao); // Adiciona fogo na lidat de posicoes de fogo
                game.Components.Add(fogoCentro); // Adiciona fogo no jogo
            }
            
            // Pego lista de paredes destrutiveis do jogo
            List<int> posicoesParedes = new List<int>();
            IEnumerable<Parede> paredes = game.Components.OfType<Parede>();
            foreach (Parede parede in paredes)
            {
                posicoesParedes.Add(parede.getPosicao());
            }

            while (poder <= poderFogo) // Faco um laco para criar o fogo dinamicamente dependendo do poder de fogo
            {
                if(esq){ // se esquerda posicao anterior da esquerda nao colidiu com algum objeto(bloco ou parede) entra no if
                    pos = posicao - 1 * poder; // pego a posicao que quero criar o fogo, baseado na posicao da bomba, e na posicao do poder que estou no momento do laco
                    esq = criaFogo(pos, posicoesParedes); // invoca o metodo que cria fogo e retorna um booleano true se nao colidir com algum objeto colidivel
                }
                // idem ao de cima, mas pela direcao da direita
                if(dir){
                    pos = posicao + 1 * poder;
                    dir = criaFogo(pos, posicoesParedes);
                    
                }
                // idem ao de cima, mas pela direcao da Cima
                if (cima)
                {
                    pos = posicao - 17 * poder;
                    cima = criaFogo(pos, posicoesParedes);
                    
                }
                // idem ao de cima, mas pela direcao da baixo
                if (baixo)
                {
                    pos = posicao + 17 * poder;
                    baixo = criaFogo(pos, posicoesParedes);             
                }
                poder++; // incremento o poder para controlar posicoes de criacao do fogo
            }            
        }

        //Metodo que cria fogo
        private bool criaFogo(int pos, List<int> posicoesParedes)
        {
            bool b = true; // Variavel de retorno para saber se continua criando fogo para direcao chamada
            if (posicoesOcupadas.Contains(pos)) // se fogo colide com objetos
            {
                b = false; // Se colidiu com algum objeto, nao irá mais criar fogo na direção atual
                if (!posicoesParedes.Contains(pos)) 
                {
                    Fogo fogo = new Fogo(game, pos, ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Se objeto que colidiu nao e uma parede, cria o fogo
                    posicoesFogo.Add(pos); // adiciona o fogo na lista de posicoes de fogo
                    game.Components.Add(fogo); // adiciona o fogo ao jogo
                }
            }
            else // Se fogo nao colide com objetos
            {
                if (!posicoesFogo.Contains(pos))// Se NAO existir fogo na posicao
                {
                    Fogo fogo = new Fogo(game, pos, ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Cria fogo
                    posicoesFogo.Add(pos); // adiciona o fogo na lista de posicoes de fogo
                    game.Components.Add(fogo); // adiciona o fogo ao jogo
                }
            }
            return b;
        }

        /// <summary>
        /// Allows the game component to draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            // Pega o Spritebatch do Services
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            // Desenha o fundo
            sBatch.Draw(textura, new Rectangle(X, Y-(H-40), W, H), new Rectangle(W * indexdir, 0, W, H), Color.White);

            base.Draw(gameTime);
        }

        // Metodo que controla mudanca de imagem da bomba
        private void mudaImagemBomba()
        {
            if (acesa) // Verifica se ela está acesa
            {
                if (contMudImg == 3) // Se sim e contador de mudanca de imagem chega ao valor desejado muda a imagem(contador implementado para a mudanca de imagem nao ser muito rapido)
                {
                    contMudImg = 0; // Contador de mudanca de imagem é zerado
                    indexdir--; // decrementa posicao da imagem
                    if (indexdir < 0) indexdir = 4; // caso a imagem acabe, volta para imagem do primeiro passo.
                }
                else // Se nao chegou o momento de mudar de imagem incrementa o contador
                {
                    contMudImg++;
                }
            }
        }

        // Metodo que movimenta bomba
        private void movimenta()
        {
            if (movimentando) // Se ela está em condicao de movimentacao
            {
                switch (direcao) // Baseado na direcao da bomba, incrementa passos na direcao
                {
                    case CIMA: 
                        andarCima(ref posicoesOcupadas);
                        break;
                    case BAIXO:
                        andarBaixo(ref posicoesOcupadas);
                        break;
                    case DIREITA:
                        andarDireita(ref posicoesOcupadas);
                        break;
                    case ESQUERDA:
                        andarEsquerda(ref posicoesOcupadas);
                        break;
                    default:
                        break;
                }
            }
        }

        private void andarDireita(ref List<int> posicoesOcupadas)
        {
            //incrementa os passos, movimentando bomba de lugar
            int XNovo = X + passo; // calcula o proximo passo
            if (colisao(ref posicoesOcupadas, XNovo, Y, posicao, DIREITA)) // Se proximo passo colidir com objeto
            {
                X = Coordenadas.retangulos[posicao].X; // Volta para posicao do ultimo quadrante livre
            }
            else // Se nao colidir
            {
                X = XNovo; // muda posicao X
                if (X + W >= Coordenadas.retangulos[posicao + 1].X + W / 2) // Se mudar de Quadrante mudo variavel posicao da bomba
                {
                    posicoesOcupadas.Remove(posicao); // Retira posicao da lista de posicoes OCupadas
                    posicao = posicao + 1; // calculo nova posicao
                    posicoesOcupadas.Add(posicao); // Adiciono nova posica na lista
                } 
            }

        }

        //Idem andarDireita
        private void andarEsquerda(ref List<int> posicoesOcupadas)
        {
            //incrementa os passos, movimentando personagem de lugar
            int XNovo = X - passo;
            if (colisao(ref posicoesOcupadas, XNovo, Y, posicao, ESQUERDA))
            {
                X = Coordenadas.retangulos[posicao].X - 10;
            }
            else
            {
                X = XNovo;
                if (X <= Coordenadas.retangulos[posicao - 1].X + W / 2)
                {
                    posicoesOcupadas.Remove(posicao);
                    posicao = posicao - 1;
                    posicoesOcupadas.Add(posicao);
                }
            }
        }

        //Idem andarDireita
        private void andarBaixo(ref List<int> posicoesOcupadas)
        {
            //incrementa os passos, movimentando personagem de lugar
            int YNovo = Y + passo;
            if (colisao(ref posicoesOcupadas, X, YNovo, posicao, BAIXO))
            {
                Y = Coordenadas.retangulos[posicao].Y + 10;
            }
            else
            {
                Y = YNovo;
                if (Y + H >= Coordenadas.retangulos[posicao + 17].Y + H / 2)
                {
                    posicoesOcupadas.Remove(posicao);
                    posicao = posicao + 17;
                    posicoesOcupadas.Add(posicao);
                }
            }
        }

        //Idem andarDireita
        private void andarCima(ref List<int> posicoesOcupadas)
        {
            //incrementa os passos, movimentando personagem de lugar
            int YNovo = Y - passo;
            if (colisao(ref posicoesOcupadas, X, YNovo, posicao, CIMA))
            {
                Y = Coordenadas.retangulos[posicao].Y - 10;
            }
            else
            {
                Y = YNovo;
                if (Y <= Coordenadas.retangulos[posicao - 17].Y + H / 2)
                {
                    posicoesOcupadas.Remove(posicao);
                    posicao = posicao - 17;
                    posicoesOcupadas.Add(posicao);
                }
            }

        }


        // Metodo que verifica colisao
        private bool colisao(ref List<int> posicoesOcupadas, int x, int y, int posicao, int direcao)
        {
            bool b1 = false, ret = false; // Variaveis de controle e de retorno
            Rectangle r = new Rectangle(x, y, W, 40); // Crio um retangulo com coordenadas atuais da bomba

            int pos1 = 0; // declaro variavel da proxima posicao que a bomba esta em direcao
            switch (direcao)
            {
                case CIMA:
                    pos1 = posicao - 17; // atribuo valor de proxima posicao
                    break;
                case BAIXO:
                    pos1 = posicao + 17; // atribuo valor de proxima posicao                  
                    break;
                case DIREITA:
                    pos1 = posicao + 1; // atribuo valor de proxima posicao
                    break;
                case ESQUERDA:
                    pos1 = posicao - 1; // atribuo valor de proxima posicao
                    break;
                default:
                    break;
            }
            r.Intersects(ref Coordenadas.retangulos[pos1], out b1); // verifico se há uma intersecção do retangulo atual da bomba com o retangulo da proxima posicao que a bomba esta indo em direcao

            if (posicoesOcupadas.Contains(pos1) && b1) // Se há interseccao
            {
                ret = true; // retorno é true(colidiu)
                movimentando = false; // e a bomba para de se movimentar
            }
            return ret;
        }

        // Metodo que adiciona movimento para bomba, caso personagem chute-a
        public void setMovimento(int direcao, int passo)
        {
            this.passo = passo; // atribui passo de personagem a bomba
            this.direcao = direcao; // atribui direcao de personagem a bomba
            this.movimentando = true; // coloca em movimento
        }


        // metodo get da variavel posicao
        public int getPosicao()
        {
            return posicao;
        }

        // metodo get da variavel player
        public int getPlayer()
        {
            return player;
        }
    }
}
